#include "game.h"
#include "gui.h"
#include "ai.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>



using std::string;



game* game::get_instance()
{
	static game inst( 0 );
	return &inst;
};



string game::create_command( std::string c, unsigned int u )
{
	std::ostringstream sout;
	sout << c << " " << u;
	return sout.str();
};



string game::create_command( std::string c, unsigned int u, unsigned int x, unsigned int y )
{
	std::ostringstream sout;
	sout << c << " " << u << " " << x << " " << y;
	return sout.str();
};



void game::get_next_ai_command()
{
	QTimer::singleShot( parameters::get_instance()->ai_parameters[ "GENERAL" ][ "ai_delay" ], current_player->ai_algorithm, SLOT ( next_command() ) );
};



void game::get_next_ai_command_now()
{
	current_player->ai_algorithm->next_command();
};



void game::start_game()
{
	current_player = players::get_instance()->get_player( 0 );
	for ( unsigned int i = 0; i < players::get_instance()->get_players_count(); ++i )
	{
		players::get_instance()->get_player( i )->set_time( parameters::get_instance()->game_parameters[ "START_TIME" ] );
	}
	if ( current_player->get_type() == ai )
	{
		current_player->ai_algorithm->get_commands();
		if ( !ai_paused )
			get_next_ai_command();
	}
	current_state = new game_state( players::get_instance()->get_players_count(), 0, players::get_instance()->pl, mapa::get_instance()->objects, mapa::get_instance()->terrain_clone() );
	current_state->reachable_places = help_computes::map_places_reachable( current_state );
	current_state->estimated_places = help_computes::map_places_estimate( current_state );
	current_state->dangerous_places = help_computes::map_places_dangerous( current_state );
	current_state->dangerous_places_nobody = help_computes::map_places_dangerous_nobody( current_state );
	current_state->attackable_places = help_computes::map_places_attackable( current_state );
	current_state->score = help_computes::game_state_estimate( current_state );
	up_to_date = true;
	set_score_to_history( current_state );
};



void game::next_turn()
{
	int current_player_id = current_player->get_id();
	for ( unsigned int i = 0; i < players::get_instance()->get_players_count(); ++i )
	{
		++current_player_id;
		current_player_id = current_player_id % players::get_instance()->get_players_count();
		if ( players::get_instance()->get_player( current_player_id )->is_in_game() )
			break;
	}
	current_player = players::get_instance()->get_player( current_player_id );
	current_player->set_time( parameters::get_instance()->game_parameters[ "START_TIME" ] );
	up_to_date = false;
	set_score_to_history( get_current_state() );
	game_gui::get_instance()->show_next_turn();
	if ( current_player->get_type() == ai )
	{
		current_player->ai_algorithm->get_commands();
		if ( !ai_paused )
			get_next_ai_command();
	}
};



player* game::test_game_end()
{
	bool end = true;

	for ( unsigned int i = 0; i < players::get_instance()->get_players_count(); ++i )
	{
		player* pl = players::get_instance()->get_player( i );
		if ( ( pl->is_in_game() ) && ( pl->units.empty() ) )
			pl->set_out();
		if ( pl == current_player )
			continue;
		if ( pl->is_in_game() )
		{
			end = false;
			break;
		}
	}
	if ( end )
	{
		set_score_to_history( get_current_state() );
		game_gui::get_instance()->show_game_end( current_player );
		return current_player;
	}
	return 0;
};



void game::command_received( player* pl, std::string command )
{
	bool failure = false;
	if ( pl != current_player )
		return;
	//no parameters
	if ( command == "NXT" )
	{
		std::cout << "NEXT TURN" << std::endl;
		next_turn();
		return;
	}
	else
	{	
		//one parameter
		std::istringstream sstr( command );
		string c;
		unsigned int u;
		sstr >> c;
		sstr >> u;
		if ( sstr.fail() )
		{
			std::cout << "COMMAND FAILURE" << std::endl;
			failure = true;
		}
		else if ( c == "SEL" )
		{
			if ( current_player->sell_unit( u ) )
				std::cout << "UNIT " << u << " SOLD SUCCESSFULLY" << std::endl;
			else
				std::cout << "CAN'T SELL UNIT " << u << std::endl;
		}
		else		
		{
			//three parameters
			unsigned int x;
			unsigned int y;
			sstr >> x;
			sstr >> y;
			if ( sstr.fail() )
			{
				std::cout << "COMMAND FAILURE" << std::endl;
				failure = true;
			}
			else if ( c == "ATT" )
			{
				if ( current_player->units[ u ]->fire( x, y ) )
					std::cout << "ATTATCK TO [" << x << "," << y << "] WITH UNIT ID " << u << " SUCCESSFUL" << std::endl;
				else
					std::cout << "CAN'T ATTATCK TO [" << x << "," << y << "] WITH UNIT ID " << u << std::endl;
			}
			else if ( c == "MOV" )
			{
				if ( current_player->units[ u ]->move( x, y ) )
					std::cout << "MOVE TO [" << x << "," << y << "] WITH UNIT ID " << u << " SUCCESSFUL" << std::endl;
				else
					std::cout << "CAN'T MOVE TO [" << x << "," << y << "] WITH UNIT ID " << u << std::endl;
			}
			else if ( c == "BUY" )
			{
				if ( current_player->buy_unit( prototypes::get_instance()->unit_pos_iterator( u )->second, x, y ) )
					std::cout << "UNIT " << prototypes::get_instance()->unit_pos_iterator( u )->second->get_name() << " BOUGHT AND PLACED TO [" << x << "," << y << "] SUCCESSFULLY" << std::endl;
				else
					std::cout << "CAN'T BUY " << prototypes::get_instance()->unit_pos_iterator( u )->second->get_name() << " AND PLACE IT TO [" << x << "," << y << "]" << std::endl;
			}
			else
			{
				std::cout << "COMMAND FAILURE" << std::endl;
				failure = true;
			}
			
		}
	}
	if ( !failure )
	{
		up_to_date = false;
		if ( current_player->get_type() == ai )
			if ( !ai_paused )
				get_next_ai_command();
		//TODO if network_game -> send command;
	}
	return;
};



player* game::get_current_player()
{
	return current_player;
};



void game::set_current_player( unsigned int n )
{
	current_player = players::get_instance()->get_player( n );
};



bool game::can_play( const player* p )
{
	return ( ( p == current_player ) && ( current_player->get_type() == local ) );
};



void game::set_paused( bool paused )
{
	ai_paused = paused;
	game_gui::get_instance()->set_paused( paused );
	if ( !paused )
		if ( current_player->get_type() == ai )
			get_next_ai_command();
};



game_state* game::get_current_state()
{
	//if state is not up to date -> update it
	if ( !up_to_date )
	{
		delete current_state;
		current_state = new game_state( players::get_instance()->get_players_count(), current_player->get_id(), players::get_instance()->pl, mapa::get_instance()->objects, mapa::get_instance()->terrain_clone() );
		current_state->reachable_places = help_computes::map_places_reachable( current_state );
		current_state->estimated_places = help_computes::map_places_estimate( current_state );
		current_state->dangerous_places = help_computes::map_places_dangerous( current_state );
		current_state->dangerous_places_nobody = help_computes::map_places_dangerous_nobody( current_state );
		current_state->attackable_places = help_computes::map_places_attackable( current_state );
		current_state->score = help_computes::game_state_estimate( current_state );
	}
	return current_state;
};



void game::set_score_to_history( game_state* state )
{
	game_state* temp_state = state->clone();

	//adds for each player his score to the history array
	for ( unsigned int i = 0; i < state->players_count; ++i )
	{
		temp_state->current_player_id = i;
		delete temp_state->dangerous_places;
		delete temp_state->dangerous_places_nobody;
		delete temp_state->attackable_places;
		temp_state->dangerous_places = help_computes::map_places_dangerous( temp_state );
		temp_state->dangerous_places_nobody = help_computes::map_places_dangerous_nobody( temp_state );
		temp_state->attackable_places = help_computes::map_places_attackable( temp_state );
		temp_state->score = help_computes::game_state_estimate( temp_state );
		(*score_history)[ temp_state->current_player_id ].push_back( temp_state->score );
	}

	delete temp_state;
};



std::vector< std::vector< double > >* game::get_score_history()
{
	return score_history;
};



mapa* mapa::get_instance()
{
	static mapa inst( 0 );
	return &inst;
};



void mapa::set_object( object* o, unsigned int x, unsigned int y )
{
	objects->set( o, x, y );
};



object* mapa::get_object( unsigned int x, unsigned int y )
{
	return objects->get( x, y );
};



bool mapa::load( const char* file )
{
	std::ifstream fin;
	fin.open( file, std::ifstream::in );
	if ( !fin.good() )
	{
		std::cout << "ERROR WHILE OPENING MAP FILE" << std::endl;
		return false;
	}
	fin >> size_x;
	fin >> size_y;
	if ( !fin.good() )
	{
		std::cout << "ERROR WHILE READING MAP FILE" << std::endl;
		return false;
	}
	terrain = new array2d< unsigned int >( size_x, size_y );
	for ( unsigned int i = 0; i < size_y; ++i )
		for ( unsigned int j = 0; j < size_x; ++j )
		{
			unsigned int t;
			fin >> t;
			terrain->set( t, j, i );
			if ( fin.fail() )
			{
				std::cout << "ERROR WHILE READING MAP FILE" << std::endl;
				return false;
			}
		}	
	objects = new array2d< object* >( size_x, size_y );
	for ( unsigned int i = 0; i < size_x; ++i )
		for( unsigned int j = 0; j < size_y; ++j )
		{
			objects->set( 0, i, j );			
		}	
	fin.close();
	std::cout << "MAP LOADED SUCCESSFULLY" << std::endl;
	return true;
};



bool mapa::load_objects( const char* file )
{
	std::ifstream fin;
	fin.open( file, std::ifstream::in );
	if ( !fin.good() )
	{
		std::cout << "ERROR WHILE OPENING OBJECTS FILE" << std::endl;
		return false;
	}
	std::string objects_line;
	for ( ; ; )
	{
		std::getline( fin, objects_line );
		if ( fin.fail() )
		{
			break;
		}
		if ( !parse_objects_line ( objects_line ) )
		{
			std::cout << "ERROR WHILE READING OBJECTS FILE" << std::endl;
			return false;
		}
	}
	fin.close();
	std::cout << "OBJECTS LOADED SUCCESSFULLY" << std::endl;
	return true;
};



bool mapa::parse_objects_line( std::string ol )
{
	if ( ol[0] == '#' )
		return true;

	std::istringstream sstr( ol );

	string type;
	sstr >> type;
	if ( type == "" )
		return true;

	sstr.ignore( ol.size(), '"' );
	string name;
	std::getline( sstr, name, '"' );

	unsigned int px;
	unsigned int py;
	unsigned int health;
	unsigned int pl_id;

	sstr >> px;
	sstr >> py;

	if ( px >= mapa::get_instance()->get_size_x() )
		return false;
	if ( py >= mapa::get_instance()->get_size_x() )
		return false;
	
	if ( type == "UNIT" )
	{
		sstr >> health;
		sstr >> pl_id;
	}
	else if ( type == "BONUS" )
	{
	}
	else
	{
		return false;
	}

	if ( sstr.fail() )
		return false;

	if ( ( terrain->get( px, py ) < 20000 ) && ( get_object( px, py ) == 0 ) )
	{
		players* plrs = players::get_instance();
		prototypes* proto = prototypes::get_instance();

		if ( type == "UNIT" )
		{
			if ( plrs->get_player( pl_id )->units.size() < parameters::get_instance()->game_parameters[ "MAX_UNITS" ] )
			{
				unit* nu = dynamic_cast< unit* >( proto->units[ name ]->clone() );
				nu->init( px, py, plrs->get_player( pl_id ), ( ( -1 ) * ( plrs->get_player( pl_id )->units.size() + 1 ) ) );
				nu->set_health( health );
				plrs->get_player( pl_id )->units.push_back( nu );
				objects->set( nu, px, py );
			}
		}
		else if ( type == "BONUS" )
		{
			bonus* nb = dynamic_cast< bonus* >( proto->bonuses[ name ]->clone() );
			nb->init( px, py );
			objects->set( nb, px, py );
		}
	}
	std::cout << "LOADED OBJECT: " << name << "\t[" << px << "," << py << "]" << std::endl;
	return true;
};



void mapa::save_objects( const char* file )
{
	std::ofstream fout;
	fout.open( file, std::ios::trunc );
	if ( fout.good() )
	{
		fout << "#\tType\tName\tPos_x\tPos_y\tHealth\tPlayer_id" << std::endl << std::endl;
		for ( unsigned int i = 0; i < size_x; ++i )
			for( unsigned int j = 0; j < size_y; ++j )
			{
				bonus* b = dynamic_cast< bonus* >( objects->get( i, j ) );	
				unit* u = dynamic_cast< unit* >( objects->get( i, j ) );
				if ( u != 0 )
				{
					fout << "UNIT\t\"" << u->get_name() << "\"\t" << u->get_x() << "\t" << u->get_y() << "\t" << u->get_health() << "\t" << u->get_player()->get_id() << std::endl;
				}
				else if ( b != 0 )
				{
					fout << "BONUS\t\"" << b->get_name() << "\"\t" << b->get_x() << "\t" << b->get_y() << std::endl;
				}
			}
		fout.close();
		std::cout << "GAME SAVED SUCCESSFULLY" << std::endl;
	}
	else
	{
		std::cout << "ERROR WHILE SAVING GAME" << std::endl;
	}
};



array2d< unsigned int >* mapa::terrain_clone()
{
	array2d< unsigned int >* nm = new array2d< unsigned int >( size_x, size_y );
	for( unsigned int i = 0; i < size_x; ++i )
		for( unsigned int j = 0; j < size_y; ++j )
			nm->set( terrain->get( i, j ), i, j );
	return nm;
};



void mapa::export_map( array2d< unsigned int >* map, const char* file, unsigned int sx, unsigned int sy )
{
	std::ofstream fout;
	fout.open( file, std::ios::trunc );
	if ( fout.good() )
	{
		for ( unsigned int i = 0; i < sx; ++i )
		{
			for( unsigned int j = 0; j < sy; ++j )
			{
				fout << map->get( j, i ) << "\t";			
			}
			fout << std::endl;
		}
		fout.close();
		std::cout << "MAP EXPORTED SUCCESSFULLY" << std::endl;
	}
	else
	{
		std::cout << "ERROR WHILE EXPORTING MAP" << std::endl;
	}
};



players* players::get_instance()
{
	static players inst( 0 );
	return &inst;
};



void players::fill_players()
{
	for ( unsigned int i = 0; i < parameters::get_instance()->game_parameters[ "MAX_PL" ]; ++i )
	{
		pl[ i ] = new player( -1, "", local );
	}
};



void players::set_player( player* p, unsigned int x )
{
	++players_count;
	
	if ( p->get_type() != network )
	{
		if ( pl[ x ]->get_id() == -1 )
		{
			//player has some loaded units
			if ( !( pl[ x ]->units.empty() ) )
			{			
				//give those units to the player
				for ( unsigned int i = 0; i < pl[ x ]->units.size(); ++i )
				{
					unit* cu = dynamic_cast< unit* >( pl[ x ]->units[ i ]->clone() );
					mapa::get_instance()->set_object( cu, cu->get_x(), cu->get_y() );
					p->units.push_back( cu );
					cu->set_player( p );
				}
			}
		}
	}
	delete pl[ x ];
	pl[ x ] = p;
};



player* players::get_player( unsigned int x )
{
	return pl[ x ];
};